#pragma once
#include "sock.hpp"
#include <iostream>
#include <functional>
#include <string>

namespace select_ns
{
    using func_t = std::function<std::string(const std::string &)>;

    const static int defaultPort = 8080;
    const static int fd_num = sizeof(fd_set) * 8;
    const static int defaultfd = -1;
    class selectServer
    {
    public:
        selectServer(func_t func, int port = defaultPort)
            : _port(port), _listensock(-1), _fdarray(nullptr), _func(func)
        {
            // logMessage(DEBUG, "%d", _port);
        }
        void initServer()
        {
            _listensock = Sock::Socket();
            Sock::Bind(_listensock, _port);
            Sock::Listen(_listensock);
            logMessage(DEBUG, "fd_set maximum support bits: %d", sizeof(fd_set) * 8); // fd_set中位图最大支持1024个bit位
            _fdarray = new int[fd_num];                                               // 数组大小为1024
            for (int i = 0; i < fd_num; i++)
            {
                _fdarray[i] = defaultfd;
            }
            _fdarray[0] = _listensock; // 服务器的第一个套接字一定是listensock，我们将他放到数组的第一个位置
        }
        void Print()
        {
            std::cout << "fd_list: ";
            for (int i = 0; i < fd_num; i++)
            {
                if (_fdarray[i] != defaultfd)
                    std::cout << _fdarray[i] << " ";
            }
            std::cout << "\n";
        }
        void Accepter(int listensock)
        {
            // listensock如果在select返回后的rfds里面，则说明listensock的读事件就绪了
            // 所以走到这里，accept调用时，一定不会发生阻塞，因为listensock读事件已经就绪了，数据拷贝的条件已经满足了
            std::string clientip;
            uint16_t clientport;
            int sock = Sock::Accept(listensock, &clientip, &clientport);
            if (sock < 0)
                return;
            logMessage(NORMAL, "accept success [%s:%d]", clientip.c_str(), clientport);

            // TODO, 这里我们能直接read/recv sock中的数据吗？不能，整个代码，只有select有资格检测事件是否就绪，所以我们要把sock交给select监视
            // 难道我们将sock返回到start函数内部吗？然后再将sock添加到rfds里？这样会不会太麻烦了？
            // 所以，一般而言，使用select编写服务器，需要程序员自己维护一个保存所有合法fd的数组

            // 1.将新的sock托管给select监视，本质其实就是将sock添加到_fdarray数组里面，因为服务器的start每次循环都会遍历fdarray数组，添加fd到rfds里面
            int i = 0;
            for (; i < fd_num; i++)
            {
                if (_fdarray[i] != defaultfd)
                    continue;
                break;
            }
            // 跳出循环有两种情况，一种是遇到非法fd的位置了，一种是数组的1024个下标全都有合法的fd，fd已经满了
            if (i == fd_num)
            {
                logMessage(WARNING, "server is full, please wait...");
                close(sock);
                // 服务器压力很大了，不能处理已经三次握手后的连接了，对不起，我服务器要发起四次挥手了，我要和你断开连接，你要是还想访问，那就下一次再试试，
                // 看看下一次服务器能不能处理完成一些连接，好空余出新的fd位置供你使用，下次你再来吧
            }
            else
            {
                _fdarray[i] = sock;
            }
            Print();
        }
        void Recver(int sock, int pos)
        {
            // 1.这样读取是有问题的！你怎么保证你一次就把数据读完了呢？如果你要是打while循环读来保证读到一个完整的报文，
            // 第一次你能保证sock上的读事件是就绪的，但第二次和之后呢？你怎么保证？保证不了的话，recv在之后的读取就又会是阻塞读取了。
            // 2.另外你怎么保证读上来的是一个完整的报文呢？
            // 如果不能保证读到一个完整的报文，又如何进行反序列化呢，变成结构化的数据呢？所以这里一定要定协议，要在应用层处理好黏包问题！！！
            // 定制协议的工作，现在先不做，等到后面写Reactor服务器时，我们会一并解决上面的两个问题的！！！
            //---序列化和反序列化的原因，以及与结构化的关系
            //(1)报文是字节流形式的，反序列化就是先保证读取一个完整字节流的数据包，然后再将字节流进行读取拆分，
            // 得到应用层通信时关心的一个个字段，然后将这些字段再组织成一个结构化的数据，通常是结构体，或类对象，这样就完整的读到了一个request请求
            //(2)得到结构化的request请求后，就可以拿到请求中的有效字段，然后在进行业务逻辑处理，将处理后的结果构建一个结构化的response响应，
            // 最后再把结构化的响应，序列化为字节流的形式(因为send发数据时，参数是char buffer这就是字节流，也是为什么要序列化的原因)，最后以send接口发送到网络中。

            //
            char buffer[1024];
            ssize_t s = recv(sock, buffer, sizeof(buffer) - 1, 0); // 设置为0表示没有特殊的标志选项，仅仅以默认方式接收数据就行
            if (s > 0)
            {
                buffer[s] = 0;
                logMessage(NORMAL, "client# %s", buffer);
            }
            else if (s == 0)
            {
                close(sock);
                _fdarray[pos] = defaultfd;
                logMessage(NORMAL, "client quit !");
                // 既然对方已经退出了，那服务器就没有必要关心这个fd了，select不应该再继续监视该文件描述符了
                return;
            }
            else
            {
                // 读取数据失败了，那也将fd关闭掉，从数组中去除fd
                close(sock);
                _fdarray[pos] = defaultfd;
                logMessage(ERROR, "client quit: %s", strerror(errno));
                return;
            }

            // 2.处理request
            std::string response = _func(buffer); // string (const char* s);string的构造函数重载
            // 3.返回response
            // write 今天直接写了，我们不想再添加wfds了，后面讲epoll的时候会把所有的问题都解决掉
            write(sock, response.c_str(), response.size());
        }
        void HandlerReadEvent(fd_set &rfds)
        {
            // 1.handler event rfds中，不仅仅只有一个fd是就绪的，可能存在多个
            // 2.目前select只处理了fd的读事件
            for (int i = 0; i < fd_num; i++)
            {
                if (_fdarray[i] == defaultfd)
                    continue; // 过滤掉非法的fd

                // 但合法的fd不一定是就绪的，很有可能是Accepter刚刚才添加到_fdarray数组里面的，还没来得及返回到start托管给select呢

                // 目前一定是listensock，只有这一个
                if (FD_ISSET(_fdarray[i], &rfds) && _fdarray[i] == _listensock) // 这里的_fdarray[i]不能写成_fdarray[0]，否则变成条件一直满足，其他分支语句无法进入
                {
                    logMessage(DEBUG, "Accepter");
                    Accepter(_listensock);
                }
                else if (FD_ISSET(_fdarray[i], &rfds))
                {
                    logMessage(DEBUG, "Recver");
                    Recver(_fdarray[i], i);
                }
                else
                {
                    // logMessage(DEBUG, "other");
                }
            }
        }
        void start()
        {
            // struct timeval timeout = {1, 0}; // 统一的列表初始化
            for (;;)
            {
                // 每一次循环前，都重新定义文件描述符集，并将其清空
                fd_set rfds;
                // fd_set wfds;
                FD_ZERO(&rfds);
                int maxfd = defaultfd;

                for (int i = 0; i < fd_num; i++)
                {
                    if (_fdarray[i] != defaultfd)
                        FD_SET(_fdarray[i], &rfds); // 将合法的fd全部添加到rfds里面（今天只关心读事件）
                    if (maxfd < _fdarray[i])
                        maxfd = _fdarray[i];
                }

                struct timeval timeout = {1, 0};
                // 写在循环内部，才能够保证每次select监听的时候，超时时间都是1s，写在外面timeout一次循环就耗尽了，后面告诉内核的都是0s，每次都非阻塞返回了。
                // timeout是输入输出型参数，每次select调用返回时，内核都会修改timeout参数，所以后面的timeout会变为0s

                // 1.阻塞式IO的写法
                // std::string clientip;
                // uint16_t clientport;
                // int sock = Sock::Accept(_listensock, &clientip, &clientport);
                // if (sock < 0)
                //     continue;

                // 2.多路转接的写法
                // int n = select(_listensock + 1, &rfds, nullptr, nullptr, &timeout);
                int n = select(maxfd + 1, &rfds, nullptr, nullptr, nullptr); // 写成阻塞式监听
                switch (n)
                {
                case 0:
                    logMessage(NORMAL, "timeout...");
                    break;
                case -1:
                    logMessage(WARNING, "select error, code: %d, err string: %s", errno, strerror(errno));
                    break;
                default:
                    // 说明有事件就绪了，目前只有一个监听事件就绪了
                    logMessage(NORMAL, "have event ready..."); // 如果你不把该监听连接accept拿上去，则get a new link会一直疯狂打印，因为每次select的返回值都大于0
                    HandlerReadEvent(rfds);
                    // HandlerWriteEvent(wfds);//如果要处理读事件，则应该再搞一个接口

                    break;
                }

                // 开始进行服务器处理逻辑
            }
        }
        ~selectServer()
        {
            if (_listensock >= 0)
                close(_listensock);
            if (_fdarray)
                delete[] _fdarray;
        }

    private:
        int _port;
        int _listensock;
        int *_fdarray;
        func_t _func;
    };
}